/**
 * Copyright 2005/6/7 Jeremy Nicholson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package generator;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;

/**
 * manages standard operations on an OpenOffice.org Writer document
 */
public class OpenOfficeDocManager {
	/**
	 * populate an empty OpenOfficeDoc from a stream
	 *
	 * @param doc
	 * @param input
	 * @throws IOException
	 */
	public static void read(OpenOfficeDoc doc, InputStream input)
			throws IOException {
		ZipInputStream zis = new ZipInputStream(input);
		BufferedReader in = new BufferedReader(new InputStreamReader(zis));
		ZipEntry entry;
		Object data = null;
		while ((entry = zis.getNextEntry()) != null) {
			if (log.isDebugEnabled()) {
				log.debug("read " + entry.getName());
			}
			if (entry.getName().startsWith("Pictures")) {
				data = getBytes(zis);
			} else {
				StringBuffer buff = new StringBuffer();
				try {
					String str;
					while ((str = in.readLine()) != null) {
						buff.append(str);
					}
				} catch (FileNotFoundException e) {
					log.error(e);
				} catch (IOException e) {
					log.error(e);
				}
				data = buff.toString();
			}
			doc.add(entry.getName(), data);
		}
		input.close();
		in.close();
		zis.close();
	}

	/**
	 * populate an empty OpenOfficeDoc from a file
	 *
	 * @param doc
	 * @param filename
	 * @throws IOException
	 */
	public static void read(OpenOfficeDoc doc, String filename)
			throws IOException {
		read(doc, new FileInputStream(filename));
	}

	/**
	 * create and populate an empty OpenOfficeDoc from a file
	 *
	 * @param filename
	 * @return
	 * @throws IOException
	 */
	public static OpenOfficeDoc read(String filename) throws IOException {
		OpenOfficeDoc doc = new OpenOfficeDoc();
		read(doc, filename);
		return doc;
	}

	public static void write(OpenOfficeDoc doc, OutputStream out)
			throws IOException {
		if (log.isDebugEnabled()) {
			log.debug("writing OpenOffice document");
		}
		ZipOutputStream zos = new ZipOutputStream(out);
		Enumeration e = doc.getFiles().keys();
		if (log.isDebugEnabled()) {
			log.debug("writing xml files");
		}
		while (e.hasMoreElements()) {
			String name = (String) e.nextElement();
			String data = (String) doc.getFiles().get(name);
			if (log.isDebugEnabled()) {
				log.debug("write " + data.length() + " chars to " + name);
			}
			ZipEntry entry = new ZipEntry(name);
			zos.putNextEntry(entry);
			zos.write(data.getBytes());
		}
		if (log.isDebugEnabled()) {
			log.debug("writing image files");
		}
		e = doc.getPictures().keys();
		while (e.hasMoreElements()) {
			String name = (String) e.nextElement();
			byte[] data = (byte[]) doc.getPictures().get(name);
			if (log.isDebugEnabled()) {
				log.debug("write " + data.length + " bytes to " + name);
			}
			ZipEntry entry = new ZipEntry(name);
			zos.putNextEntry(entry);
			zos.write(data);
		}
		zos.close();
	}

	public static void write(OpenOfficeDoc doc, String name, String dir)
			throws IOException {
		String filename = dir + name + "." + doc.getFileExtension();
		write(doc,filename);
	}

	public static void write(OpenOfficeDoc doc, String filename)
			throws IOException {
		File file = new File(filename);
		int i = 0;
		filename = filename.substring(0, filename.length() - 4);
		while (file.exists()) {
			if (log.isDebugEnabled()) {
				log.debug("file already exists: " + filename);
			}
			i++;
			file = new File(filename + "_" + i + ".sxw");
		}
		OutputStream out = new FileOutputStream(file);
		write(doc, out);
		out.close();
	}

	/**
	 * find the name of an entry matching a regex pattern
	 * @param zipFile
	 * @param pattern
	 * @return
	 */
	public static String getEntryName(ZipFile zipFile, String pattern) {
		if (log.isDebugEnabled()) {
			log.debug("looking for " + pattern);
		}
		ZipEntry entry = null;
		Enumeration en = zipFile.entries();
		while (en.hasMoreElements()) {
			Object obj = en.nextElement();
			entry = (ZipEntry) obj;
			String s = entry.getName();
			if (Pattern.matches(pattern, s)) {
				return s;
			} else {
				entry = null;
			}
		}
		log.error(pattern + " is unknown in this zip");
		return null;
	}

	public static byte[] getBytes(ZipFile zipFile, String name)
			throws IOException {
		ZipEntry entry = null;
		Enumeration en = zipFile.entries();
		boolean found = false;
		while (!found && en.hasMoreElements()) {
			Object obj = en.nextElement();
			entry = (ZipEntry) obj;
			String s = entry.getName();
			if (Pattern.matches(name, s)) {
				found = true;
			} else {
				entry = null;
			}
		}
		if (null == entry) {
			log.error(name + " is unknown in this zip");
			return null;
		}
		InputStream inputStream = zipFile.getInputStream(entry);
		return getBytes(inputStream);
	}

	/**
	 * extract the bytes from the file with the provided name
	 *
	 * @param name
	 * @return the bytes
	 * @throws IOException
	 */
	public static byte[] getBytes(String filename) throws IOException {
		FileInputStream inputStream = new FileInputStream(filename);
		return getBytes(inputStream);
	}

	/**
	 * extract the bytes from a file or a zip file entry. Note that this method
	 * does not close the stream, that is the responsibility of the calling
	 * program.
	 *
	 * @param is
	 *            a stream of data
	 * @return the bytes
	 * @throws IOException
	 */
	public static byte[] getBytes(InputStream is) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		byte[] buf = new byte[1024];
		int len;
		while ((len = is.read(buf)) > 0) {
			out.write(buf, 0, len);
		}
		byte[] bytes = out.toByteArray();
		out.close();
		if (log.isDebugEnabled()) {
			log.debug("read " + bytes.length + " bytes from stream");
		}
		return bytes;
	}

	/**
	 * extract the text from a zip file entry
	 * @param zipFile
	 * @param filename
	 * @return text in the entry
	 * @throws IOException
	 */
	public static String getText(ZipFile zipFile, String entryname)
			throws IOException {
		byte[] bytes = getBytes(zipFile, entryname);
		String s = "";
		if (null != bytes) {
			s = new String(bytes);
		}
		return s;
	}

	/**
	 * extract the text contained in a file
	 * @param filename
	 * @return textual contents
	 * @throws FileNotFoundException
	 */
	public static String getText(String filename) throws FileNotFoundException {
		InputStream data = new FileInputStream(filename);
		BufferedReader in = new BufferedReader(new InputStreamReader(data));
		StringBuffer buff = new StringBuffer();
		try {
			String str;
			while ((str = in.readLine()) != null) {
				buff.append(str);
			}
			in.close();
		} catch (FileNotFoundException e) {
			log.error(e);
		} catch (IOException e) {
			log.error(e);
		}
		return buff.toString();
	}

	/**
	 *
	 * @param doc
	 * @return a dom4j xml Document
	 * @throws DocumentException
	 */
	public static Document getContentDocument(OpenOfficeDoc doc)
			throws DocumentException {
		SAXReader saxReader = new SAXReader();
		String xmlStr = doc.getContent();
		StringReader reader = new StringReader(xmlStr);
		Document document = saxReader.read(reader);
		return document;
	}

	public static void setContentDocument(OpenOfficeDoc doc, Document xml) {
		doc.setContent(xml.asXML());
	}

	public static void dumpContent(OpenOfficeDoc doc) throws IOException {
		dumpContent(doc, "content");
	}

	public static void dumpContent(OpenOfficeDoc doc, String name)
			throws IOException {
		File file = new File("C:/" + name + ".xml");
		FileOutputStream fos = new FileOutputStream(file);
		fos.write(doc.getContent().getBytes());
		fos.close();
	}

	public static void dumpManifest(OpenOfficeDoc doc) throws IOException {
		dumpContent(doc, "manifest");
	}

	public static void dumpManifest(OpenOfficeDoc doc, String name)
			throws IOException {
		File file = new File("C:/" + name + ".xml");
		FileOutputStream fos = new FileOutputStream(file);
		fos.write(doc.getManifest().getBytes());
		fos.close();
	}

	private final static Log log = LogFactory.getLog(OpenOfficeDocManager.class);

}